﻿/* mvcForum
 * Copyright (C) 2010-2011 Steen F. Tøttrup
 * http://mvcforum.org/
 */

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;

namespace mvcForum.Core {

	public class Post : ICustomPropertyHolder {

		#region Constructors
		public Post() { }
		public Post(ForumUser author, Topic topic, String subject, String body, String ip)
			: this(author, topic, subject, body, ip, null) {
		}
		public Post(ForumUser author, Topic topic, String subject, String body, String ip, Post replyTo) {
			this.Topic = topic;
			this.Subject = subject;
			this.Body = body;
			this.IP = ip;
			this.Indent = 0;
			if (replyTo != null) {
				this.ReplyToPost = replyTo;
				this.Indent = replyTo.Indent + 1;
			}
			this.Posted = DateTime.UtcNow;
			this.Author = author;
			this.AuthorName = author.Name;
			this.SpamReporters = 0;
			this.SpamScore = 0;
		}
		#endregion

		#region Properties
		public Int32 Id { get; set; }
		[Required]
		public Int32 Position { get; set; }
		[Required]
		public Int32 Indent { get; set; }
		/// <summary>
		/// The subject of the post.
		/// </summary>
		[StringLength(Constants.FieldLengths.Subject)]
		[Required]
		public String Subject { get; set; }
		/// <summary>
		/// The content of the post.
		/// </summary>
		[StringLength(Int32.MaxValue)]
		[Required]
		public String Body { get; set; }
		/// <summary>
		/// The IP address of the author.
		/// </summary>
		[StringLength(Constants.FieldLengths.IPAddress)]
		[Required]
		public String IP { get; set; }
		private DateTime? edited;
		public DateTime? Edited {
			get {
				return this.edited;
			}
			set {
				this.edited = value.Handle();
			}
		}
		public PostFlag Flag {
			get {
				return (PostFlag)this.FlagValue;
			}
			private set {
				this.FlagValue = (Int32)value;
			}
		}
		[Required]
		public Int32 FlagValue { get; set; }
		[StringLength(Constants.FieldLengths.EditReason)]
		public String EditReason { get; set; }
		/// <summary>
		/// A boolean indicating whether or not this post has been changed by a moderator.
		/// </summary>
		[Required]
		public Boolean ModeratorChanged { get; set; }
		/// <summary>
		/// If the post has been deleted, the reason for deleting the post.
		/// </summary>
		[StringLength(Constants.FieldLengths.DeleteReason)]
		public String DeleteReason { get; set; }
		/// <summary>
		/// The topic this post was posted in.
		/// </summary>
		[Required]
		public Int32 TopicId { get; set; }
		public virtual Topic Topic { get; set; }
		public Int32? ReplyToPostId { get; set; }
		public virtual Post ReplyToPost { get; set; }
		/// <summary>
		/// The Id of the author.
		/// </summary>
		[Required]
		public Int32 AuthorId { get; set; }
		public virtual ForumUser Author { get; set; }
		/// <summary>
		/// The name of the author.
		/// </summary>
		[StringLength(Constants.FieldLengths.Username)]
		[Required]
		public String AuthorName { get; set; }
		private DateTime posted;
		/// <summary>
		/// The timestamp of when the post was posted.
		/// </summary>
		[Required]
		public DateTime Posted {
			get {
				return this.posted;
			}
			set {
				this.posted = value.Handle();
			}
		}
		[Required]
		public Int32 SpamScore { get; set; }
		[Required]
		public Int32 SpamReporters { get; set; }
		/// <summary>
		/// Do NOT access this property directly. We WILL change the schema from time to time!
		/// Use the extension method on <see cref="ICustomPropertyHolder"/> to get your custom properties.
		/// </summary>
		public String CustomProperties { get; set; }
		/// <summary>
		/// Do NOT access this property directly. We WILL change the schema from time to time!
		/// Use the extension method on <see cref="ICustomPropertyHolder"/> to get your custom properties.
		/// </summary>
		public XDocument CustomData { get; set; }
		#endregion

		#region Public methods
		/// <summary>
		/// Method for reporting the post as spam.
		/// TODO: This is not used in any way yet!
		/// </summary>
		/// <param name="spamScore">The score to "award" the post.</param>
		public void ReportSpam(Int32 spamScore) {
			this.SpamReporters++;
			this.SpamScore += SpamScore;
		}

		/// <summary>
		/// Method for setting a flag.
		/// </summary>
		/// <param name="flag">The flag that has to be set.</param>
		public void SetFlag(PostFlag flag) {
			// Let's set the flag!
			this.Flag = flag;
		}

		/// <summary>
		/// Method for changing an existing post.
		/// </summary>
		/// <param name="user">The user changing the content of the post.</param>
		/// <param name="subject">The changed subject.</param>
		/// <param name="body">The changed body.</param>
		public void Update(ForumUser user, String subject, String body) {
			if (user.Id != this.Author.Id) {
				// TODO: Throw an exception?! This is a moderation, a reason must be given.
			}
			this.Update(user, subject, body, String.Empty);
		}

		/// <summary>
		/// Method for moderating the post.
		/// </summary>
		/// <param name="user">The user moderating the post.</param>
		/// <param name="subject">The subject of the post after moderation.</param>
		/// <param name="body">The body of the post after moderation.</param>
		public void Update(ForumUser user, String subject, String body, String reason) {
			if (this.Subject != subject || this.Body != body || !String.IsNullOrEmpty(reason)) {
				this.Edited = DateTime.UtcNow;
				if (user.Id != this.Author.Id) {
					this.ModeratorChanged = true;
				}
				// Store the (maybe) changed subject.
				this.Subject = subject;
				// Store the (maybe) changed content.
				this.Body = body;
				// Was a reason given?
				if (!String.IsNullOrWhiteSpace(reason)) {
					// Store the reason for changing the post.
					this.EditReason = reason;
				}
			}
		}

		/// <summary>
		/// Mark a post as deleted.
		/// </summary>
		/// <param name="user">The user deleting post.</param>
		/// <param name="reason">The reason for deleting the post.</param>
		public void Delete(ForumUser user, String reason) {
			// TODO: Can the user actually delete this post??
			this.DeleteReason = reason;
			if (this.Author.Id != user.Id) {
				this.ModeratorChanged = true;
			}
			this.SetFlag(PostFlag.Deleted);
			// TODO: What about posts that are "replies" to this post!??!
		}

		public void Undelete(ForumUser user, String reason) {
			this.DeleteReason = reason;
			if (this.Author.Id != user.Id) {
				this.ModeratorChanged = true;
			}
			this.SetFlag(PostFlag.None);
		}

		/// <summary>
		/// The collection of attachments on this post.
		/// </summary>
		public virtual ICollection<Attachment> Attachments { get; set; }
		#endregion
	}
}